Skip to content

fix(catalog): publish cache assets without overwriting entries - #3739

Merged
jrusso1020 merged 3 commits into
mainfrom
fix/security-catalog-cache-writes
Sep 8, 2026
Merged

fix(catalog): publish cache assets without overwriting entries#3739
jrusso1020 merged 3 commits into
mainfrom
fix/security-catalog-cache-writes

Conversation

@jrusso1020

Copy link
Copy Markdown
Collaborator

Catalog asset references and large embedded data URIs checked for an existing hashed output and then wrote by pathname. A competing file or symlink could appear between those operations and be overwritten. Both callers now share cache publication that stages complete bytes in a private sibling directory, then atomically links them into place only if the destination is still absent. This addresses CodeQL #851 and #852.

Existing cache entries remain untouched, including dangling links, and cache hits require no staging or writable directory. Hashes, URLs, MIME handling, externalization thresholds and result counts are preserved. Cache misses reject a symlinked cache directory. Cleanup is best effort so it cannot mask a write/publication failure or turn a successful publication into an error. The output directory's ancestors remain trusted against hostile concurrent parent renames; cleanup failure may leave private staging residue. Directory-copy overwrite behavior is unchanged.

Validation: all 274 script/catalog tests (188 Node + 86 Vitest), script types, dependency builds, lint/format and signed hooks pass. Twenty new cases cover both callers: competing files/live or dangling links, existing dangling entries, external directory links, complete publication, cache reuse, partial-write cleanup and publication/cleanup failures. The competing-entry, dangling-link, directory-link and partial-write witnesses fail against main. The new suite joins the existing catalog source-read suite in the Windows lane.

Optional Fallow remains red locally: eight complexity findings, seven classified as inherited, and no duplication groups. The new cache helper has cyclomatic complexity 8; its branches enforce cache-hit preservation, directory type, competing-entry handling and cleanup. No suppressions were added. Fresh CI, Windows, CodeQL and Magi's review of the current commit remain merge gates.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

Fallow audit report

Found 8 findings.

Details
Severity Rule Location Description
major fallow/high-crap-score scripts/catalog-payload-assets.ts:95 'localReferences' has CRAP score 56.0 (threshold: 30.0, cyclomatic 7)
minor fallow/high-crap-score scripts/catalog-payload-assets.ts:124 'probableReferences' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
critical fallow/high-crap-score scripts/catalog-payload-assets.ts:174 'readProjectFile' has CRAP score 110.0 (threshold: 30.0, cyclomatic 10)
major fallow/high-crap-score scripts/catalog-payload-assets.ts:213 'cacheAsset' has CRAP score 72.0 (threshold: 30.0, cyclomatic 8)
minor fallow/high-crap-score scripts/catalog-payload-assets.ts:251 'processAssets' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
minor fallow/high-crap-score scripts/catalog-payload-assets.ts:342 'hostedPaths' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
minor fallow/high-crap-score scripts/catalog-payload-assets.ts:355 'downloadMirrorPrefix' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
major fallow/high-crap-score scripts/catalog-payload-assets.ts:390 'hostItemDirectory' has CRAP score 90.0 (threshold: 30.0, cyclomatic 9)

Generated by fallow.

@miga-heygen

Copy link
Copy Markdown
Contributor

Closing — this PR has merge conflicts and has been inactive. Thank you for the contribution! Please feel free to reopen with a rebased branch.

@miga-heygen miga-heygen closed this Sep 8, 2026
@jrusso1020 jrusso1020 reopened this Sep 8, 2026

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head security/concurrency review at 6cde0fab99d2dd3f81c227932cf7e922f44684a4.

  • scripts/catalog-payload-assets.ts:217 preserves cache hits—including dangling leaves—without requiring mkdir, staging, or write access.
  • scripts/catalog-payload-assets.ts:221-229 stages complete bytes in an atomically created private sibling and makes linkSync the sole publication operation; only EEXIST is swallowed, so a competing regular file or live/dangling link is never replaced.
  • scripts/catalog-payload-assets.ts:230-234 keeps cleanup best-effort without masking the original write/link failure or turning a successful publication into an error.
  • scripts/catalog/catalog-payload-assets.cache-race.test.ts:95-162 covers both callers across cache reuse, competing entries, directory links, partial writes, publication failure, and cleanup failure. The two focused catalog race suites pass 44/44 locally.
  • .github/workflows/windows-render.yml:487-495 resolves the rebase conflict correctly: the newer frame-staging test remains, and both catalog file-operation suites run in the Windows lane.

No blocking findings. The disclosed remaining pathname trust boundary for hostile concurrent ancestor swaps is real but not widened by this patch; the optional Fallow failure is the disclosed cyclomatic-8 helper plus inherited findings, with no suppression. Required Test and Windows checks are still pending at review time, so branch protection remains the merge gate.

Verdict: APPROVE
Reasoning: The implementation gives both hashed-cache writers one atomic, no-replace publication boundary while preserving existing hit, hash, URL, MIME, threshold, and count behavior; the adversarial tests exercise the relevant state transitions and cleanup paths.

— Magi

@jrusso1020
jrusso1020 merged commit d66cd6d into main Sep 8, 2026
47 of 48 checks passed
@jrusso1020
jrusso1020 deleted the fix/security-catalog-cache-writes branch September 8, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants